﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Xml;
using System.Data;

namespace SQLProxy
{
    class ClientInteraction
    {
        private Socket clientSocket;
        private Thread listenerThread;
        private DatabaseConnector dbc;
        private DataSet ds;
        private NetworkStream stream;
        private ClientData clientData;

        public ClientInteraction(Socket clientSocket)
        {
            Console.WriteLine("");
            Console.WriteLine("> Cliente conectado!");
            this.clientSocket = clientSocket;
            listenerThread = new Thread(new ThreadStart(ReceiveData));
            listenerThread.Start();
        }

        private string readString(Stream reader)
        {
            int bytesRead, l;
            byte[] packetSize = new byte[sizeof(Int32)];
            byte[] data;
            bytesRead = reader.Read(packetSize, 0, packetSize.Length);
            l = BitConverter.ToInt32(packetSize, 0);
            data = new byte[l];
            bytesRead = reader.Read(data, 0, data.Length);

            if (bytesRead == 0)
            {
                return null;
            }
            else
            {
                return Encoding.UTF8.GetString(data, 0, data.Length);
            }
        }

        private bool writeString(Stream writer, string text)
        {
            byte[] data = Encoding.UTF8.GetBytes(text);

            if (data.Length == 0)
            {
                return false;
            }
            else
            {
                writer.Write(BitConverter.GetBytes(data.Length), 0, sizeof(Int32));
                writer.Write(data, 0, data.Length);
                return true;
            }
        }

        private DataSet readDataSet(Stream reader)
        {
            DataSet dataSet = new DataSet();
            string xmlDataSet = readString(reader);

            if (xmlDataSet != null)
            {
                dataSet.ReadXml(new StringReader(xmlDataSet));
                return dataSet;
            }
            else
            {
                return null;
            }
        }

        private bool writeDataSet(Stream writer, DataSet dataSet)
        {
            StringWriter strWriter = new StringWriter();
            dataSet.WriteXml(strWriter, XmlWriteMode.WriteSchema);
            string xmlDataSet = strWriter.ToString();

            return writeString(writer, xmlDataSet);
        }

        public void ReceiveData()
        {
            stream = new NetworkStream(clientSocket);

            try
            {
                if (clientSocket.Connected)
                {
                    dbc = new DatabaseConnector();
                    // read header info
                    try
                    {
                        Console.WriteLine("> Lendo informacao do cliente!");
                        string header = readString(stream);
                        // split the header into items
                        // [0] - type; [1] - username; [2] - password
                        string[] items = header.Split(';');
                        clientData = new ClientData(items[1], items[2]);

                        switch (items[0])
                        {
                            case "read-residence":
                                readResidence();
                                break;
                            case "read-login":
                                readLogin();
                                break;
                            case "write-residence":
                                writeResidence();
                                break;
                        }
                    }
                    catch (IOException ex)
                    {
                        Console.WriteLine("> Erro de leitura de dados do cliente!");
                    }
                    catch (System.Data.SqlClient.SqlException ex)
                    {
                        Console.WriteLine("> Erro no acesso a base de dados!");
                    }
                }
            }
            catch (Exception)
            {
                Console.WriteLine("> Erro na conexao do socket!");
            }

            Console.WriteLine("> Socket finalizado!");
            Console.WriteLine("");
        }

        private bool writeResidence()
        {
            try
            {
                // read dataset
                DataSet resiDS = readDataSet(stream);
                DataTableReader dtReader = resiDS.CreateDataReader();
                // write version vectors to dataset for serialization to client
                DataSet versionDS = new DataSet();

                // read all residences from the stream and write to database
                while (dtReader.Read())
                {
                    Residence auxRes = new Residence(dtReader);
                    versionDS.Merge(dbc.updateResidence(clientData, auxRes),false);
                }

                // write a string saying that data is valid
                writeString(stream, "valid");

                // write version vector to client
                writeDataSet(stream, versionDS);
                
                Console.WriteLine("> Salvou dados de residencias!");

                return true;
            }
            catch (Exception ex)
            {
                // write a string saying that data is invalid
                writeString(stream, "invalid");

                Console.WriteLine("> Nao conseguiu salvar dados de residencias!");

                return false;
            }
        }

        private bool readResidence()
        {
            try
            {
                // get the user write-set
                DataSet userVersionVector = readDataSet(stream);

                ds = dbc.getResidenceDataSet(clientData, userVersionVector);

                if (ds != null)
                {
                    // write a string saying that data is valid
                    writeString(stream, "valid");

                    writeDataSet(stream, ds);

                    Console.WriteLine("> Escreveu dados de residencias para cliente!");
                    return true;
                }
                else
                {
                    // write a string saying that data is invalid
                    writeString(stream, "inconsistent");

                    Console.WriteLine("> Problemas na escrita de dados!");
                    return false;
                }


            }
            catch (Exception ex)
            {
                // write a string saying that data is invalid
                writeString(stream, "invalid");

                Console.WriteLine("> Problemas na escrita de dados!");
                return false;
            }
        }

        private bool readLogin()
        {
            try
            {
                ds = dbc.getTechnicianInfo(clientData);

                // write a string saying that data is valid
                writeString(stream, "valid");

                writeDataSet(stream, ds);

                Console.WriteLine("> Escreveu dados de tecnico para cliente!");
                return true;
            }
            catch (Exception)
            {
                // write a string saying that data is invalid
                writeString(stream, "invalid");

                Console.WriteLine("> Problemas na escrita de dados!");
                return false;
            }
        }
    }
}
